﻿// Copyright © Microsoft Corporation.  All Rights Reserved.
// This code released under the terms of the 
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
// This is sample code only, do not use in production environments
namespace Microsoft.ALMRangers.TFSProcTemplateCust.CustomControls
{
    using System;
    using System.Collections.Specialized;
    using System.Globalization;
    using System.Windows;
    using System.Windows.Controls;
    using Microsoft.TeamFoundation.WorkItemTracking.Client;
    using Microsoft.TeamFoundation.WorkItemTracking.Controls;

    /// <summary>
    /// Implements a checkbox custom control.
    /// 
    /// To implement a work item custom control all we need to do is implement the <see cref="Microsoft.TeamFoundation.WorkItemTracking.Controls.IWorkItemControl"/> 
    /// interface and derive from a custom control in WPF or Winforms control. 
    /// 
    /// 
    /// In this case we simple derive from <see cref="System.Windows.Controls.CheckBox"/> since it provides all functionality we
    /// need and we do not intended to have any UI other than a simple checkbox.
    /// </summary>
    public sealed class CheckBoxControl : CheckBox, IWorkItemControl
    {
        /// <summary>
        /// Cache for the field (if any) that is associated with the custom control
        /// </summary>
        private Field fieldCache = null;

        /// <summary>
        /// Has the control been initialized?
        /// </summary>
        bool initialized = false;
        object syncObject = new object();
        private bool readOnlyMode = false;
        private WorkItem workItem = null;

        public CheckBoxControl() : base()
        { 
        }

        #region UI events

        /// <summary>
        /// Event that is called when the checkbox is clicked at the UI level.
        /// We update the field value according to the checkbox state.
        /// 
        /// </summary>
        protected override void OnClick()
        {
            base.OnClick();
            
            // If the control is set in read only mode we don't anything.
            if (ReadOnly == false && Field != null)
            {
                try
                {
                    BeforeUpdate(); // Raise event if necessary
                    FieldValue = IsChecked;
                }
                finally
                {
                    AfterUpdate(); // Raise event if necessary
                }
            }
        }

        #endregion        
        
        #region IWorkItemControl Members

        public event EventHandler AfterUpdateDatasource;
        public event EventHandler BeforeUpdateDatasource;

        /// <summary>
        /// Property bag, that holds all parameters passed into our control.
        /// Two types of parameters are supported. "plain" parameters, which are 
        /// simple strings that are passed into the control in the attributes 
        /// of the custom control or (since 2010) as a XML-
        /// This is used to hold the parameters passed into our control. The key
        /// The XML is stored string encoded in the property bag with the key
        /// "innerxml".
        /// </summary>
        public StringDictionary Properties { get; set; }

        /// <summary>
        /// The work item field name that the control is associated with.
        /// association is not mandatory, so if no field has been associated with
        /// the control this field is not set.
        /// </summary>
        public string WorkItemFieldName { get; set; }

        /// <summary>
        /// Called when the host asks for the control to be cleared.
        /// </summary>
        public void Clear()
        {
            if (IsThreeState)
            {
                IsChecked = null;
            }
            else
            {
                IsChecked = false;
            }
        }

        public void FlushToDatasource()
        {
            // This is called when we should "save" the value into the fields. 
            // It may happen when the control is being unloaded or the host decides
            // it's time for the field to be saved. 
            // In this specific control there is no need to save, since we update
            // the field as soon as the UI is changed
        }

        /// <summary>
        /// This method is called whenever the data source is invalidated. Basically the host
        /// is asking the control to redraw itself.
        /// For example this is called when the control is loaded or when the control is unloaded, 
        /// or when our field value has changed outside our control
        /// </summary>
        ///         
        public void InvalidateDatasource()
        {
            lock (syncObject)
            {
                if (initialized == false)
                {
                    initialized = true;
                    if (ValidateParameters() == false) return;
                    LoadParameters();
                }
            }
            UpdateControl();
        }        
        
        /// <summary>
        /// Is the control in read only mode (controlled by the host via a parameter)
        /// If the control is in read only mode, we should display the field value
        /// associated with the control but not allow the
        /// user to change it's value
        /// </summary>
        public bool ReadOnly 
        {
            get
            {
                return readOnlyMode;
            }
            set
            {
                readOnlyMode = value;
                this.IsEnabled = readOnlyMode == false;
            }
        }

        /// <summary>
        /// This method is called so our control can save the reference
        /// to IServiceProvider. Since we don't need to interact with 
        /// any services we really don't do anything.
        /// </summary>
        /// <param name="serviceProvider"></param>
        public void SetSite(IServiceProvider serviceProvider)
        {            
        }
        
        /// <summary>
        /// The reference to the work item in which the control is being used.
        /// We will need to cast this into <see cref="Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem"/>
        /// </summary>
        public object WorkItemDatasource
        {
            get
            {
                return workItem;
            }
            set
            {
                workItem = (WorkItem)value;
            }
        }
        
        #endregion

        #region helpers

        /// <summary>
        /// Triggers the event (if defined) BeforeUpdateDataSource
        /// </summary>
        private void BeforeUpdate()
        {
            if (BeforeUpdateDatasource != null)
            {
                BeforeUpdateDatasource(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Triggers the event (if defined) AfterUpdateDataSource
        /// </summary>
        private void AfterUpdate()
        {
            if (AfterUpdateDatasource != null)
            {
                AfterUpdateDatasource(this, EventArgs.Empty);
            }
        }

        #endregion

        #region Control implementation

        /// <summary>
        /// The field that is associated with the control.
        /// 
        /// If no field is associated (not specified via the FieldName XML attribute) 
        /// then it returns null.        
        /// </summary>
        private Field Field
        {
            get
            {
                if (fieldCache != null)
                {
                    return fieldCache;
                }

                // There are times when WorkItemDataSource can be null (for example control is being unloaded)
                // we also check if we are not associated with any field
                if (WorkItemDatasource != null && string.IsNullOrWhiteSpace(WorkItemFieldName) == false)
                {
                    try
                    {
                        WorkItem workItem = WorkItemDatasource as WorkItem;
                        if (workItem != null)
                        {
                            fieldCache = workItem.Fields[WorkItemFieldName];
                        }
                    }
                    catch
                    {
                        fieldCache = null;
                    }
                }

                return fieldCache;
            }
        }

        /// <summary>
        /// Abstracts us from where the field value is stored.
        /// 
        /// Allows us to get/set the value of the field that is associated
        /// with the control.
        /// 
        /// The field value is stored in the work item field that is associated
        /// with the control
        /// </summary>
        private bool? FieldValue
        {
            get
            {
                if (Field != null && Field.Value != null)
                {
                    var currentFieldValue = Field.Value as bool?;
                    if (currentFieldValue != null)
                    {
                        return currentFieldValue;
                    }
                }
                return null;
            }
            set
            {
                if (Field == null) return;

                if (value.HasValue == false)
                {
                    Field.Value = null;
                }
                else
                {
                    Field.Value = value.Value;                    
                }
            }
        }

        /// <summary>
        /// Updates the state of the checkbox based on the field
        /// value.
        /// 
        /// If the value is null and three state mode is enabled then we
        /// set it accordingly, otherwise null means false.
        /// </summary>
        private void UpdateControl()
        {
            bool? current = FieldValue;

            if (current == null)
            {
                if (IsThreeState) IsChecked = null;
                else IsChecked = false;
            }
            else
            {
                if (IsChecked != current)
                {
                    IsChecked = current;
                }
            }
        }

        /// <summary>
        /// Loads the parameters and sets the behavior ouf the control.
        /// We support one parameter "ThreeState" which is a boolean predicate
        /// that indicates if the checkbox works in a three state mode (true,false and null)
        /// </summary>
        private void LoadParameters()
        {
            if (Properties["ThreeState"] != null)
            {
                IsThreeState = string.Compare(Properties["ThreeState"], bool.TrueString, StringComparison.OrdinalIgnoreCase) == 0;
            }
        }

        /// <summary>
        /// Validates the parameters.
        /// We only accept fields of Boolean type <see cref="Microsoft.TeamFoundation.WorkItemTracking.Client.FieldType"/>
        /// 
        /// If an invalid type is passed, we place disable the checkbox and issue a message box error.
        /// 
        /// If no field is associated with the control we also signal an error and disable the checkbox
        /// </summary>
        /// <returns>True if all parameters are valid. False otherwise</returns>
        private bool ValidateParameters()
        {
            if (string.IsNullOrWhiteSpace(WorkItemFieldName))
            {
                MessageBox.Show(String.Format(CultureInfo.InvariantCulture, "No field has been associated with checkbox control {0}.\nControl will be disabled.", Properties["Name"]),"Error - No field associated",MessageBoxButton.OK,MessageBoxImage.Error);
                this.IsEnabled = false;

                return false;
            }

            if (Field != null && Field.FieldDefinition.FieldType != FieldType.Boolean)
            {
                MessageBox.Show(String.Format(CultureInfo.InvariantCulture, "Invalid field type for checkbox control {0}. Only Boolean field type are supported and {1} is of type {2}\nControl will be disabled.", Properties["Name"], WorkItemFieldName, Field.FieldDefinition.FieldType), "Error - Invalid Field Type", MessageBoxButton.OK, MessageBoxImage.Error);
                this.IsEnabled = false;

                return false;
            }

            return true;
        }
        #endregion
    }
}
